// 搜索
<template>
  <div class="search">
    <!-- 顶部菜单 -->
    <div class="search-icons">
      <i
        v-for="icon in menuIcons"
        :key="icon"
        :class="icon"
        class="iconfont"
        @click="menuClick(icon)"
      />
    </div>

    <!-- 搜索框 -->
    <el-input size="small" v-model="keyword" clearable placeholder="搜索" />

    <!-- 搜索结果提示 -->
    <div class="search-result" v-if="fileCount">
      {{ fileCount }}个文件中有{{ resultCount }}个结果
      <el-tooltip
        class="box-item"
        effect="dark"
        content="如果内容变更，请刷新结果"
        placement="left"
      >
        <el-link type="primary" :underline="false">
          <el-icon><InfoFilled /></el-icon>
        </el-link>
      </el-tooltip>
    </div>

    <!-- 搜索列表展示 -->
    <el-scrollbar v-if="searchResult.length">
      <Collapase :data="searchResult" />
    </el-scrollbar>
  </div>
</template>

<script setup lang="ts">
import { Utils } from "../../utils";
import { menuIcons } from "./config";
import { editor, languages } from "monaco-editor";
import Collapase from "./components/Collapase.vue";
import { ISearchResultItem } from "../../interface";
import { InfoFilled } from "@element-plus/icons-vue";
import { IFileTreeItem } from "../../interface/FileTree";
import WebContainerAPI from "../../class/WebContainerAPI";
import { fixMonacoEditorEnvError } from "../../utils/monaco";
import { useFileTreeStore } from "../../pinia/useFileTreeStore";
import { nextTick, onMounted, reactive, ref, watch } from "vue";

// 定义最大字符长度
const MAX_LINE_CONTENT = 10;

// 搜索关键词
const keyword = ref("");
watch(keyword, search);

// 记录初始文件内容，不需要每次都去查找，文件内容不会经常换的，提供刷新按钮即可
let fileContentList = reactive(<IFileTreeItem[]>[]);

// 记录搜索结果
const searchResult = reactive(<ISearchResultItem[]>[]);

// 记录多少个文件
const fileCount = ref(0);
// 记录多少个结果
const resultCount = ref(0);

/**
 * 输入框输入事件
 *  @param { string } keyword 事件回调参数，指的是输入框的值，并非上诉定义的 keyword 搜索关键词
 *  findMatches 执行搜索操作，需要一个编辑器实例,执行 editor.getModel().findMatches('icon')
 *  revealLineInCenter revealPositionInCenter revealRangeInCenter setPosition setSelections
 */
async function search(keyword: string) {
  searchResult.length = 0;
  fileCount.value = 0;
  resultCount.value = 0;

  if (!keyword) return;
  await nextTick();

  /**
   * 实现思路：
   * 1. 获取所有文件
   * 2. 遍历所有文件，获取文件内容
   * 3. 循环调用 const model = editor.createModel(value, language);
   * 4. 调用 model.findMatches(keyword) 获取结果
   */

  if (!fileContentList.length) await updateFileContent();

  for (let i = 0; i < fileContentList.length; i++) {
    const { label, content, id } = fileContentList[i];
    const suffix = Utils.getFileSuffix(label);
    // 调用 createModel
    const languageModelId = getLanguageModel(suffix);
    const model = editor.createModel(content as string, languageModelId);
    const result = model.findMatches(keyword, false, false, false, null, false);
    if (!result.length) return;

    // 处理下 根据 range 获取内容 getLineContent 再截取指定 startColumn endColumn 的字符串，
    // 显示面板需要高亮指定字符，通过 replace 替换实现
    const LineContents = <string[]>[];
    result.forEach((item) => {
      // 获取参数 - !!!! 暂时不考虑跨行问题
      const { startLineNumber, startColumn } = item.range;

      // 如果选词跨度小于 10 ，则取全部字符串，不然就截取 从 index-2 到 index +5
      const lineContent = model.getLineContent(startLineNumber);
      if (!lineContent) return;

      const start = lineContent.length < MAX_LINE_CONTENT ? 0 : startColumn - 6;

      // 这里存在一个 BUG，如果搜索字符前后有 < \ / > 等字符，会出现显示异常
      LineContents.push(
        lineContent
          .slice(start, lineContent.length)
          // 此处注意空格 转义，否则会被替换掉
          .replace(/\//, " /")
          .replaceAll(keyword, `<span>${keyword}</span>`)
      );
    });

    // 不然记录搜索结果
    searchResult.push({ filename: label, filepath: id, result, LineContents });
    resultCount.value += LineContents.length;
    fileCount.value++;
  }
}

/**
 * 解析文件路径、内容
 */
async function updateFileContent() {
  fileContentList.length = 0;
  const files = <IFileTreeItem[]>[];
  const fileTree = await useFileTreeStore().readAsFileTreeData();
  const parseFile = (data: IFileTreeItem[]) => {
    // 如果是文件夹，递归
    data.forEach((item) => {
      if (item.type === "Folder" && item.children)
        return parseFile(item.children);
      // 是文件直接添加
      files.push(item);
    });
  };

  parseFile(fileTree || []);

  // 解析完成后，读取文件内容
  for (let i = 0; i < files.length; i++) {
    const { id } = files[i];
    const content = await WebContainerAPI.readFileContent(id);
    files[i].content = content || "";
  }

  fileContentList.push(...files);
  return Promise.resolve();
}
/**
 * 通过传入的后缀 获取Monaco language
 * @param s 后缀 suffix
 * @returns language
 */
function getLanguageModel(s: string) {
  const languageList = languages.getLanguages();
  const language = languageList.find((i) => i.extensions?.includes(`.${s}`));
  return language?.id || "javascript";
}

/**
 * 顶部菜单点击事件
 */
async function menuClick(icon: string) {
  switch (icon) {
    case "icon-shuaxin":
      // 刷新
      await updateFileContent();
      search(keyword.value);
      break;

    default:
      break;
  }
}

onMounted(async () => {
  fixMonacoEditorEnvError();
  await updateFileContent();
});
</script>
<script lang="ts">
export default {
  name: "searchComponents",
};
</script>

<style lang="css" scoped>
.search {
  position: relative;
}
.search-icons {
  border-bottom: solid 1px var(--t-main-border-color);
  display: flex;
  justify-content: flex-end;
  padding-bottom: 10px;
  margin-bottom: 10px;
}
.search-icons i {
  color: var(--t-main-font-color);
  margin-left: 10px;
  cursor: pointer;
}
.search-icons i:hover {
  color: #409eff;
}

.search-result {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 12px;
  margin: 10px 0;
}
.search-result .el-link {
  margin-left: 4px;
}
</style>
